#include "Winsock2ServerSocket.h"

net::Winsock2ServerSocket::Winsock2ServerSocket(int portNumber)
{
	this->portNumber = portNumber;
	listenSocket = INVALID_SOCKET;

	WSADATA wsaData;
    int startUpResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (startUpResult != 0 ||
		(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2))
    {
        cout << "WSAStartup failed with error: " << startUpResult << endl;
    }

	obtainedAddress = NULL;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_PASSIVE;

    stringstream port;
	port << portNumber;

	int addressResult = getaddrinfo(NULL, port.str().c_str(), &hints, &obtainedAddress);
    if (addressResult != 0)
    {
        cout << "getaddrinfo failed with error: " << addressResult << endl;
        WSACleanup();
    }

    listenSocket = socket(obtainedAddress->ai_family, obtainedAddress->ai_socktype, obtainedAddress->ai_protocol);
    if (listenSocket == INVALID_SOCKET)
    {
        cout << "socket failed with error: " << WSAGetLastError() << endl;
        freeaddrinfo(obtainedAddress);
        WSACleanup();
    }
}

net::Winsock2ServerSocket::~Winsock2ServerSocket(void)
{
	WSACleanup();
}

int net::Winsock2ServerSocket::doBind(void) throw (NetworkException)
{
	if (listenSocket == INVALID_SOCKET)
	{
		throw NetworkException("Unable to bind on invalid socket.");
	}

	sockaddr_in localAddress;
	localAddress.sin_family = AF_INET ;
	localAddress.sin_port = htons(portNumber);

	memset(&(localAddress.sin_zero), 0, 8);
	localAddress.sin_addr.s_addr = INADDR_ANY ;
	
	int bindResult = bind(listenSocket, (sockaddr*)&localAddress, sizeof(localAddress));
    if (bindResult == SOCKET_ERROR)
    {
        freeaddrinfo(obtainedAddress);
        closesocket(listenSocket);
        WSACleanup();
        throw NetworkException(WSAGetLastError());
    }

    freeaddrinfo(obtainedAddress);
	return bindResult;
}

int net::Winsock2ServerSocket::doListen(void) throw (NetworkException)
{
	int iResult = listen(listenSocket, SOMAXCONN);
    if (iResult == SOCKET_ERROR)
    {
        closesocket(listenSocket);
        WSACleanup();
        throw NetworkException(WSAGetLastError());
    }
	return iResult;
}

SOCKET net::Winsock2ServerSocket::doAccept(void) throw (NetworkException)
{
	SOCKET ClientSocket = accept(listenSocket, NULL, NULL);
	if (ClientSocket == INVALID_SOCKET)
	{
		closesocket(listenSocket);
		WSACleanup();
		throw NetworkException(WSAGetLastError());
	}
	return ClientSocket;
}

void net::Winsock2ServerSocket::doClose(void)
{
	closesocket(listenSocket);
}

void net::Winsock2ServerSocket::setOption(OptionLevel ol, OptionName on) throw (NetworkException)
{
	int level = 0;
	int name = 0;
	BOOL TRUE_BOOLEAN_VALUE = TRUE;
    int BOOLEAN_OPTION_LENGTH = sizeof(BOOL);
	if (ol == TCP)
	{
		level = IPPROTO_TCP;
	}
	if (on == NO_DELAY)
	{
		name = TCP_NODELAY;
	}
	if (level > 0 && name > 0)
	{
		if (setsockopt(listenSocket, level, name, (char*)&TRUE_BOOLEAN_VALUE, BOOLEAN_OPTION_LENGTH) == SOCKET_ERROR)
		{
			string message("Unable to set option ");
			message.append(getSocketOptionReadable(on)).append(" for socket level ").append(getSocketLevelReadable(ol)).append(".");
			throw NetworkException(message);
		}
	}
}

string net::Winsock2ServerSocket::getSocketOptionReadable(int option)
{
	string readableOption = "";
	if (option == NO_DELAY)
	{
		readableOption = "TCP_NODELAY";
	}
	return readableOption;
}

string net::Winsock2ServerSocket::getSocketLevelReadable(int level)
{
	string readableLevel = "";
	if (level == TCP)
	{
		readableLevel = "IPPROTO_TCP";
	}
	return readableLevel;
}

string net::Winsock2ServerSocket::toString(void)
{
	int i = 1;
	struct addrinfo* ptr = NULL;
	string info = "";
	stringstream ss;
	for(ptr=obtainedAddress; ptr != NULL; ptr=ptr->ai_next)
	{
		ss << ptr->ai_flags;
		ss.str();
		info.append("[Flags= ").append(ss.str());
		ss.clear();
        info.append("|Family= ");
        switch (ptr->ai_family)
		{
            case AF_UNSPEC:
                info.append("Unspecified");
                break;
            case AF_INET:
                info.append("AF_INET (IPv4)");
                break;
            case AF_INET6:
                info.append("AF_INET6 (IPv6)");
                break;
            case AF_NETBIOS:
                info.append("AF_NETBIOS (NetBIOS)");
                break;
            default:
				ss << ptr->ai_family;
				info.append("Other(").append(ss.str()).append(")");
				ss.clear();
                break;
        }
        info.append("|Socket type=");
        switch (ptr->ai_socktype) {
            case 0:
                info.append("Unspecified");
                break;
            case SOCK_STREAM:
                info.append("SOCK_STREAM (stream)");
                break;
            case SOCK_DGRAM:
                info.append("SOCK_DGRAM (datagram)");
                break;
            case SOCK_RAW:
                info.append("SOCK_RAW (raw)");
                break;
            case SOCK_RDM:
                info.append("SOCK_RDM (reliable message datagram)");
                break;
            case SOCK_SEQPACKET:
                info.append("SOCK_SEQPACKET (pseudo-stream packet)");
                break;
            default:
				ss << ptr->ai_socktype;
				info.append("Other(").append(ss.str()).append(")");
				ss.clear();
                break;
        }
        info.append("|Protocol=");
        switch (ptr->ai_protocol) {
            case 0:
                info.append("Unspecified");
                break;
            case IPPROTO_TCP:
                info.append("IPPROTO_TCP (TCP)");
                break;
            case IPPROTO_UDP:
                info.append("IPPROTO_UDP (UDP)");
                break;
            default:
				ss << ptr->ai_protocol;
				info.append("Other(").append(ss.str()).append(")");
				ss.clear();
                break;
        }
		ss << ptr->ai_addrlen;
		info.append("|sockaddr length=").append(ss.str());
		ss.clear();
		if (ptr->ai_canonname != 0)
		{
			info.append("|Canonical name= ").append(ptr->ai_canonname);
		}
		info.append("]");
    }
	return info;
}
